home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Cream of the Crop 25
/
Cream of the Crop 25.iso
/
program
/
synctree.zip
/
SYNCTREE.C
< prev
next >
Wrap
C/C++ Source or Header
|
1997-03-13
|
15KB
|
600 lines
/* Copyright (c) 1997, Cerious Software Inc.
**
** This source code may be used and modified freely for personal use.
** Commercial use requires permission from Cerious Software, Inc.
**
** Cerious Software, Inc.
** 1515 Mockingbird Ln. Suite 910
** Charlotte, NC 28209 USA
** Tel: 704-529-0200
** Fax: 704-529-0497
** E-mail: pcrews@cerious.com
** CompuServe: 71501,2470
*/
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
/* Synctree
**
** Synctree [-tn] [-q] cmdfile
**
** This program synchronizes the files in two directory trees
** using the information provided in a separate file (the 'cmdfile').
**
** -tn Sets trace level to n (0-9)
** -q Set test mode (no actual copies occur)
**
** cmdfile structure:
**
** Dir1: <First base directory>
** Dir2: <Second base directory>
** Include: <File mask(s) to include, separated by semicolons>
** Exclude: <File mask(s) to exclude, separated by semicolons>
** SkipDir: <Subdirectories to exclude>
**
** A sample cmdfile would be:
**
** Dir1: C:\Projects
** Dir2: \\REMOTE\C\Projects
** Include: *
** Exclude: *.obj;*.sbr;*.aps;*.res;*.tdb;*.dll;*.exe;*.bsc;*.pch
** SkipDir: Release;Debug;Help;Manual;*.old
*/
#define UPCHAR(c) ((int)AnsiUpper((LPSTR)c))
#define LOCHAR(c) ((int)AnsiLower((LPSTR)c))
#define ISDOTDIR(f) ((f)[0]=='.' && ((f)[1]==0 || (f)[1] =='.'))
#define SLASH '\\'
#define ERR_MEMORY 1
#define ERR_OPENCMD 2
#define ERR_NOTHING 3
#define ERR_NODIR 4
#define ERR_USAGE 5
#define ERR_NOCMD 6
#define ERR_CANTCOPY 7
#define ERR_DIRFILE 8
#define ERR_DIRACCESS 9
#define ERR_PATHSPEC 10
#define ERR_MAKEDIR 11
static LPCTSTR error_msg[] = {
"Success",
"Insufficient memory for buffers (last requested = %d bytes)",
"Unable to open command file: %s",
"Neither path has any files to process (%s, %s)",
"Both paths must be specified",
"Usage: synctree [-t<n>] [-q] cmdfile",
"Command file not specified",
"Error copying %s to %s: %s",
"Cannot create directory %s; file with same name exists",
"Cannot access directory %s",
"An invalid path was specified (%s)",
"Cannot create directory %s"
};
/* The file entry table contains the following information. The
** date/time is maintained in DOS format for comparisons, as comparing
** a time from a Dos or Windows 95 file system and an NTFS file system
** otherwise results in "false positives" for changes.
*/
typedef struct tagFINFO {
LPTSTR name; /* File name */
DWORD written; /* Date/time written (DOS format <Gag>) */
} FINFO;
/* The following information is maintained for each directory tree
*/
typedef struct tagTREE {
int count;
int len;
LPTSTR buf;
FINFO *finfo;
LPTSTR *includes;
LPTSTR *excludes;
LPTSTR *skipdirs;
TCHAR dir[_MAX_PATH];
} TREE;
int trace_level = 1, trace = 0;
TREE tree1, tree2;
TCHAR cmdfile[_MAX_PATH];
TCHAR directory1[_MAX_PATH];
TCHAR directory2[_MAX_PATH];
TCHAR includes[_MAX_PATH];
TCHAR excludes[_MAX_PATH];
TCHAR skipdirs[_MAX_PATH];
BOOL testmode;
TCHAR highname[_MAX_PATH];
int newcopied[3], updated[3], skipped;
/* Trace() - Output a trace message
*/
static void Trace(int level, LPCTSTR msg, ...)
{
va_list ap;
TCHAR out[1000];
if (level <= trace)
{
va_start(ap, msg);
if (level)
memset(out, ' ', level);
vsprintf(out + level, msg, ap);
fputs(out, stdout);
fputc('\n', stdout);
}
}
/* Abort() - Output an error message & exit
*/
static void Abort(int err, ...)
{
va_list ap;
va_start(ap, err);
vfprintf(stderr, error_msg[err], ap);
fputc('\n', stderr);
exit(err);
}
/* Warning() - Output a warning message
*/
static BOOL Warning(int err, ...)
{
va_list ap;
va_start(ap, err);
vfprintf(stderr, error_msg[err], ap);
fputc('\n', stderr);
return FALSE;
}
/* Realloc() - realloc() with an abort if memory isn't available
*/
static LPVOID Realloc(LPVOID pv, int newsize)
{
if ( (pv = realloc(pv, newsize)) == NULL)
Abort(ERR_MEMORY, newsize);
return pv;
}
/* Malloc() - malloc() with an abort if memory isn't available
*/
static LPVOID Malloc(int newsize)
{
LPVOID pv;
if ( (pv = malloc(newsize)) == NULL)
Abort(ERR_MEMORY, newsize);
return pv;
}
/* Calloc() - calloc() with an abort if memory isn't available
*/
static LPVOID Calloc(int cnt, int size)
{
LPVOID pv;
if ( (pv = calloc(cnt, size)) == NULL)
Abort(ERR_MEMORY, cnt*size);
return pv;
}
/* DirName() - Extract the directory name from a file specification
*/
static LPTSTR DirName(LPCTSTR fullspec, LPTSTR buf)
{
LPTSTR t;
strcpy(buf, fullspec);
if ( (t = strrchr(buf, SLASH)) != NULL)
*t = 0;
else if ( (t = strrchr(buf, ':')) != NULL)
t[1] = 0;
return buf;
}
/* FullSpec() - Generate a full path with directory & filename
*/
static LPTSTR FullSpec(LPTSTR out, LPCTSTR dirname, LPCTSTR filename)
{
if (filename[0] == SLASH)
sprintf(out, "%s%s", dirname, filename);
else
sprintf(out, "%s%c%s", dirname, SLASH, filename);
return out;
}
/* AddToList() - Add a file entry to the list of files
**
** The list of files is initially built as a single long character array;
** thus, realloc'ing it is most likely to succeed without having to
** move the block.
*/
static void AddToList(TREE *ptree, LPCTSTR name, FILETIME filetime)
{
WORD dosdate, dostime;
DWORD written;
int newlen = ptree->len + strlen(name) + sizeof(DWORD) + 1;
FileTimeToDosDateTime(&filetime, &dosdate, &dostime);
written = MAKELONG(dostime, dosdate);
ptree->buf = Realloc(ptree->buf, newlen);
memcpy(ptree->buf + ptree->len, &written, sizeof(DWORD));
strcpy(ptree->buf + ptree->len + sizeof(DWORD), name);
ptree->len = newlen;
ptree->count++;
}
/* MakeArray() - Build file entry array from list of files
*/
static int MakeArray(TREE *ptree)
{
int i;
LPTSTR buf = ptree->buf;
ptree->finfo = Calloc(ptree->count+1, sizeof(FINFO));
for (i=0; i<ptree->count; i++)
{
memcpy(&ptree->finfo[i].written, buf, sizeof(DWORD));
buf += sizeof(DWORD);
ptree->finfo[i].name = buf;
buf += strlen(buf) + 1;
}
memset(&ptree->finfo[i].written, 0xff, sizeof(DWORD));
memset(highname, 0xff, sizeof(highname));
ptree->finfo[i].name = highname;
return ptree->count;
}
/* MatchMask() - See if a file name matches a mask
*/
static BOOL MatchMask(LPCTSTR pname, LPCTSTR pmask)
{
LPCSTR pn = pname, pm;
if (pmask == NULL || pname == NULL)
return FALSE;
for (pm=pmask; *pn && *pm; pm=AnsiNext(pm))
{
switch (*pm)
{
case '?':
if (*pn)
pn = AnsiNext(pn);
break;
case '*':
while (*pn && *pn != pm[1])
pn = AnsiNext(pn);
break;
default:
if (UPCHAR(*pn) != UPCHAR(*pm))
return FALSE;
if (IsDBCSLeadByte(*pn) && UPCHAR(pn[1]) != UPCHAR(pm[1]) )
return FALSE;
pn = AnsiNext(pn);
}
}
return (*pm || *pn) ? FALSE : TRUE;
}
/* MatchMasks() - See if a file name matches a list of masks
*/
static BOOL MatchMasks(LPCTSTR name, LPCTSTR *masks)
{
for(; *masks; masks++)
if (MatchMask(name, *masks))
return TRUE;
return FALSE;
}
/* ReadTree() - Read a directory tree
*/
static int ReadTree(LPCTSTR path, TREE *ptree)
{
TCHAR wild[_MAX_PATH];
TCHAR fullname[_MAX_PATH];
WIN32_FIND_DATA fdata;
HANDLE hfind;
LPTSTR name = fdata.cFileName;
Trace(++trace_level, "%s", path);
FullSpec(wild, path, "*.*");
if ( (hfind = FindFirstFile(wild, &fdata)) != INVALID_HANDLE_VALUE)
{
do {
if (ISDOTDIR(name) || name[0] == 0)
continue;
if (fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
if (!MatchMasks(name, ptree->skipdirs))
ReadTree(FullSpec(fullname, path, name), ptree);
} else if (MatchMasks(name, ptree->includes) && !MatchMasks(name, ptree->excludes))
AddToList(ptree, FullSpec(fullname, path, name), fdata.ftLastWriteTime);
} while (FindNextFile(hfind, &fdata));
FindClose(hfind);
}
trace_level--;
return ptree->count;
}
/* ReportCopyError() - Report error from copying a file
*/
static BOOL ReportCopyError(LPCTSTR from, LPCTSTR toname)
{
TCHAR buf[1000];
DWORD err = GetLastError();
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, sizeof(buf), NULL);
return Warning(ERR_CANTCOPY, from, toname, buf);
}
/* MakeDirIfNeeded() - Create a directory if needed (and parent directory(ies))
*/
static BOOL MakeDirIfNeeded(char *newdir)
{
TCHAR parentdir[_MAX_PATH];
DWORD attr, err;
attr = GetFileAttributes(newdir);
if (attr != 0xffffffff && (attr & FILE_ATTRIBUTE_DIRECTORY))
return TRUE; /* Directory already exists */
if (attr != 0xffffffff)
return Warning(ERR_DIRFILE, newdir); /* File exists with same name */
err = GetLastError() & 0x7fff;
if (err != ERROR_PATH_NOT_FOUND && err != ERROR_FILE_NOT_FOUND)
return Warning(ERR_DIRACCESS, newdir); /* Cannot access directory */
if (!CreateDirectory(newdir, NULL))
{
err = GetLastError();
DirName(newdir, parentdir);
if (!parentdir[0])
Abort(ERR_PATHSPEC, newdir); /* Something way wrong */
if (!MakeDirIfNeeded(parentdir))
return FALSE;
if (!CreateDirectory(newdir, NULL))
return Warning(ERR_MAKEDIR, newdir);
}
return TRUE;
}
/* Copy() - Copy a file, with trace and count
*/
static BOOL Copy(LPCTSTR from, LPCTSTR toname, int which)
{
LPCTSTR what = testmode ? "Would copy" : "Copying";
TCHAR dirname[MAX_PATH];
Trace(1, "%s %s", what, toname);
if (!testmode)
{
if (!MakeDirIfNeeded(DirName(toname, dirname)))
return FALSE;
if (!CopyFile(from, toname, FALSE))
return ReportCopyError(from, toname);
}
newcopied[which]++;
return TRUE;
}
/* Update() - Update a file, with trace and count
*/
static BOOL Update(LPCTSTR from, LPCTSTR toname, int which)
{
LPCTSTR what = testmode ? "Would update" : "Updating";
Trace(1, "%s %s", what, toname);
if (!testmode)
if (!CopyFile(from, toname, FALSE))
ReportCopyError(from, toname);
updated[which]++;
return TRUE;
}
/* ProcessLists() - Compare the two file trees and perform
** appropriate operations
*/
static void ProcessLists(TREE *ptree1, TREE *ptree2)
{
int pos1 = 0, pos2 = 0, c;
int lx1 = strlen(ptree1->dir), lx2 = strlen(ptree2->dir);
LPTSTR name1, name2;
DWORD time1, time2;
TCHAR tofile[_MAX_PATH];
while (pos1 < ptree1->count || pos2 < ptree2->count)
{
name1 = ptree1->finfo[pos1].name + lx1; /* Names without base dir */
name2 = ptree2->finfo[pos2].name + lx2;
time1 = ptree1->finfo[pos1].written;
time2 = ptree2->finfo[pos2].written;
c = stricmp(name1, name2);
if (c < 0) /* Catch up tree1 */
Copy(ptree1->finfo[pos1++].name, FullSpec(tofile, ptree2->dir, name1), 1);
else if (c > 0) /* Catch up tree2 */
Copy(ptree2->finfo[pos2++].name, FullSpec(tofile, ptree1->dir, name2), 2);
else {
if (time1 < time2)
Update(ptree2->finfo[pos2].name, FullSpec(tofile, ptree1->dir, name2), 2);
else if (time1 > time2)
Update(ptree1->finfo[pos1].name, FullSpec(tofile, ptree2->dir, name1), 1);
else {
Trace(2, "Skipping same %s", ptree2->finfo[pos2].name + lx2);
skipped++;
}
pos1++;
pos2++;
}
}
}
/* PrintSummary() - Output a summary of the operations performed
*/
static void PrintSummary(void)
{
#define PLURAL(n) (n),((n==1)?"":"s")
fprintf(stdout, "%7d new file%s copied from %s to %s\n", PLURAL(newcopied[1]),
directory1, directory2);
fprintf(stdout, "%7d new file%s copied from %s to %s\n", PLURAL(newcopied[2]),
directory2, directory1);
fprintf(stdout, "%7d file%s updated from %s to %s\n", PLURAL(updated[1]),
directory1, directory2);
fprintf(stdout, "%7d file%s updated from %s to %s\n", PLURAL(updated[2]),
directory2, directory1);
fprintf(stdout, "%7d file%s were identical and skipped\n", PLURAL(skipped));
}
/* SortName() - Sort comparison routines (by file name, not case-sensitive)
*/
static int SortName(const void *pe1, const void *pe2)
{
const FINFO *pc1 = (FINFO *)pe1;
const FINFO *pc2 = (FINFO *)pe2;
return stricmp(pc1->name, pc2->name);
}
/* SplitMasks() - Split a ;-separated list into an array of pointers
*/
static int SplitMasks(LPTSTR masks, LPTSTR **parray)
{
LPTSTR pm;
LPTSTR *pa = NULL;
int c = 0;
for (pm = strtok(masks, ";"); pm; pm = strtok(NULL, ";"))
{
pa = Realloc(pa, sizeof(LPTSTR) * (c+1));
pa[c++] = pm;
}
pa = Realloc(pa, sizeof(LPTSTR) * (c+1)); /* Terminating entry */
pa[c] = NULL;
*parray = pa;
return c;
}
/* GetCmdFile() - Read the command file and set variables
*/
static void GetCmdFile(LPTSTR cmdfile)
{
FILE *fp;
TCHAR linein[MAX_PATH * 2];
LPTSTR parg;
if ( (fp = fopen(cmdfile, "r")) == NULL)
Abort(ERR_OPENCMD, cmdfile);
while (fgets(linein, sizeof(linein), fp))
{
if (linein[0] == '!' || linein[0] == ';' || linein[0] == '/')
continue;
if ( (parg = strchr(linein, ':')) == NULL)
continue;
linein[strlen(linein)-1] = 0; /* Strip NL */
*parg++ = 0;
while (*parg == ' ' || *parg == '\t')
parg++;
if (!stricmp(linein, "dir1"))
strcpy(directory1, parg);
else if (!stricmp(linein, "dir2"))
strcpy(directory2, parg);
else if (!stricmp(linein, "include"))
strcpy(includes, parg);
else if (!stricmp(linein, "exclude"))
strcpy(excludes, parg);
else if (!stricmp(linein, "skipdir"))
strcpy(skipdirs, parg);
}
if (directory1[0] == 0 || directory2[0] == 0)
Abort(ERR_NODIR);
fclose(fp);
}
/* GetOptions() - Get command line options
** (where oh where is getopt when you want it?)
*/
static void GetOptions(int argc, char *argv[])
{
int i;
for (i=1; i<argc && argv[i] && argv[i][0] == '-'; argc--, argv++)
{
switch (argv[i][1])
{
case 't': trace = argv[i][2]-'0'; break;
case 'q': testmode = TRUE; break;
default: Abort(ERR_USAGE);
}
}
for (; i<argc; i++)
strcat(strcat(cmdfile, argv[i]), " ");
if (strlen(cmdfile) > 1)
cmdfile[strlen(cmdfile)-1] = 0;
else
Abort(ERR_NOCMD);
}
/* main()
*/
int main(int argc, char *argv[])
{
GetOptions(argc, argv);
GetCmdFile(cmdfile);
SplitMasks(includes, &tree1.includes);
SplitMasks(excludes, &tree1.excludes);
SplitMasks(skipdirs, &tree1.skipdirs);
tree2.includes = tree1.includes;
tree2.excludes = tree1.excludes;
tree2.skipdirs = tree1.skipdirs;
Trace(1, "Reading %s...", directory1);
strcpy(tree1.dir, directory1);
ReadTree(directory1, &tree1);
MakeArray(&tree1);
Trace(1, "Reading %s...", directory2);
strcpy(tree2.dir, directory2);
ReadTree(directory2, &tree2);
MakeArray(&tree2);
if (tree1.count == 0 && tree2.count == 0)
Abort(ERR_NOTHING, directory1, directory2);
Trace(1, "Sorting %s list...", directory1);
qsort(tree1.finfo, tree1.count, sizeof(FINFO), SortName);
Trace(1, "Sorting %s list...", directory2);
qsort(tree2.finfo, tree2.count, sizeof(FINFO), SortName);
Trace(1, "Processing lists");
ProcessLists(&tree1, &tree2);
PrintSummary();
return 0;
}